Skip to content

fix(fields): one home for the datetime display convention - #7621

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7443-datetime-compact-style
Sep 5, 2026
Merged

fix(fields): one home for the datetime display convention#7621
os-sam merged 3 commits into
mainfrom
claude/issue-7443-datetime-compact-style

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #7443

Implements the maintainer ruling of 2026-09-04, decision batch #34, option B — comment 5539935824 — which amends batch #29 (ruling 1′) on the call shape only. One home for the datetime display convention: formatDateTime gains a named 'compact' style that is today's cell face byte-for-byte, the cell renders through it, and the third independently authored spelling converges onto it. The published signature formatDateTime(value, options?) is unchanged; 'compact' rides in options.style.

Revision history: the first delivery (head 460232c3) shipped shape A — a positional style parameter inserted before options — which the ruling refuses by name. This revision (head a43aa8101, after merging main at a00db9ef9) puts the signature back and carries the style inside options. Session: session_01KbJQ1y1J12nZxYzFWhP8Q3.

The shape, as shipped

  • packages/core/src/utils/date-display.ts:231export function formatDateTime(value: string | Date | number, options?: DateDisplayOptions): string, byte-identical to main's signature. Inside, if (options?.style === 'compact') joins the two halves from formatDateTimeCompactParts; otherwise the default face, unchanged.
  • DateDisplayOptions gains style?: string (date-display.ts:75), read by formatDateTime only. formatDate still takes its style positionally and does not read the key — the symmetric long-run shape (both functions reading options.style) is additive on formatDate and, per the ruling, not this card's.
  • packages/fields/src/index.tsx:885 computes const style = field?.format || 'compact' and :909 calls formatDateTime(date, { style, locale, t }); the compact branch keeps its two-span DOM sourced from formatDateTimeCompactParts.
  • packages/components/src/renderers/complex/data-table.tsx:774formatDateTime(new Date(ts), { locale: language }) for the datetime branch; the date-only branch keeps its own bag on purpose (date has the same two-convention split datetime just had: data-table's date-only cell keeps a year that formatDate drops #7620, below).
  • The three items of ruling 1′ stand exactly as delivered: 'compact' byte-identical, DateTimeCellRenderer reads field, formatCellValue converges on the default face with no pixel moved.

Call-site migrations reverted — 13 measured, against the ruling's ten

The A shape had moved options from position two to three at every in-repo call. All of them are back to origin/main byte for byte (git diff a00db9ef9 on the five files is empty):

file kind call expressions
packages/core/src/utils/dataset-format.ts runtime 1
packages/plugin-gantt/src/ObjectGantt.tsx runtime 2
packages/core/src/utils/__tests__/dataset-format.date.test.ts assertion 3
packages/fields/src/__tests__/date-formatter-residue-4272.test.ts assertion 4
packages/plugin-dashboard/src/__tests__/DatasetWidget.dateMeasure.test.tsx assertion 3

That is 3 runtime + 10 assertion call expressions across 2 + 3 files. The ruling counts "ten (2 runtime, 8 assertions)": the two runtime files match, the per-expression count does not. Recorded rather than silently adopting either number. The two runtime calls this PR itself adds (the cell and the data-table) were never migrations and are written in the B shape directly.

Pins — every leg re-run at a43aa8101

Readings computed through the built @object-ui/core dist, for 2024-07-04T07:00:00.000Z:

locale former cell (the inlined bags) formatDateTime(v, { style: 'compact', locale }) identical
en-US 7/4/2024 7:00 am 7/4/2024 7:00 am yes
zh 2024/7/4 上午7:00 2024/7/4 上午7:00 yes
de-DE 4.7.2024 7:00 am 4.7.2024 7:00 am yes
locale data-table before (its former bag) after (formatDateTime default) moved
en-US Jul 4, 2024, 07:00 AM Jul 4, 2024, 07:00 AM no
zh 2024年7月4日 07:00 2024年7月4日 07:00 no
de-DE 4. Juli 2024, 07:00 4. Juli 2024, 07:00 no
  • The card's en-US literal 7/4/2024 7:00 am — green. Two-span DOM (time muted, ml-2) — green in all three locales. || default: an authored empty-string format stays on the compact face ('' || 'compact' is 'compact'; ?? would yield '') — green. date cell untouched (formatDate; 'short' renders Jul 4, '24, the default stays relative) — green. The objectui#4272 shape formatDateTime(v, { locale }) (zh renders 2024年1月5日 08:30, en renders Jan 5, 2024, 08:30 AM) — green, from the restored residue pin.
  • New in this revision: an arity pin — formatDateTime.length is 2 (formatDate.length is 3, untouched) — that turns red the moment a positional slot is inserted again; plus the Date formatter is half-localized: future relative forms and absolute timestamps stay en-US on a non-English locale #4272 call shape and the compact face selected beside a locale, both through options.
  • Verbose run of the two pin files at a43aa8101: Test Files 2 passed (2) / Tests 33 passed (33) (26 in the fields pin, 7 in the data-table pin).

Verification — all at a43aa8101, from the repo root, heavy runs through the shared verify lock

  • pnpm exec vitest run --maxWorkers=2 packages/fields/ packages/components/src/__tests__/data-tableTest Files 142 passed (142) / Tests 2246 passed (2246), lock VERDICT command-exit 0.
  • pnpm exec vitest run --maxWorkers=2 packages/core/ packages/plugin-gantt/ packages/plugin-dashboard/Test Files 273 passed (273) / Tests 3821 passed (3821), lock VERDICT command-exit 0.
  • pnpm --filter @object-ui/core|fields|components run type-check (tsc --noEmit && tsc -p tsconfig.test.json), after building the ten-package dependency closure — exit 0 with 0 TS errors, each; --listFiles confirms the PR's test files are inside the test programs (fields 2, components 1, core 1). plugin-gantt and plugin-dashboard are byte-identical to origin/main and are not re-type-checked here; CI runs the full farm.
  • Gates, each exit 0 with its own verdict line: check-changeset-presence (5 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)), check-changeset-no-major (No changeset declares a major bump), check-changeset-fixed (All workspace packages are in the changeset fixed group), check-changeset-overwrite (1 changeset(s) added, 0 modified, 0 deleted), check:self-import, check:phantom-deps, check:control-bytes (OK (scanned 6303 tracked text file(s))).
  • eslint over the five changed source and test files, --format json: 0 errors, 184 warnings — no-explicit-any, react-refresh/only-export-components and this file's pre-existing react-hooks findings. 15 sit on PR-added lines: 11 no-explicit-any (the as any casts the pins and the cell share with DateCellRenderer) and 4 react-refresh on the barrel's re-export line, which already carried them on main. Narrowing evidence: the population is eslint's own config (js + tseslint recommended, not type-aware — zero projectService or parserOptions in eslint.config.js, custom rules are per-file AST rules), the file count 5 is read from the JSON output, and no configured rule reads across files, so this diff cannot move an untouched file's verdict.
  • check:readme-exports is NOT MEASURED locally (it needs all 40 packages built; 10 are). CI's README Export Check passed at 460232c3 with the identical export set.

Ablation — two legs; predictions written before running, mutation proven on disk, restore proven by blob hash

No rebuild is owed: vitest.config.mts aliases every @object-ui/* specifier to packages/*/src (36 aliases), and chunk 1 passed with zero dist/ built, so the mutation sits on the resolution path directly. Each leg proved the edit landed by grep -c on the injected marker (1) and on the deleted text (0) before running anything; ran datetime-compact-style-7443.test.tsx (26 tests); then restored with git checkout HEAD -- ABSOLUTE_PATH under trap … EXIT INT TERM, proven by git hash-object equal to the HEAD blob (8d571622…) and an empty git diff HEAD --name-only.

leg predicted observed
A — delete the options?.style === 'compact' branch 7 red: the joined face in three locales, the en-US literal, the join identity, the "default is not the compact face" half, compact selected beside de-DE Tests 7 failed / 19 passed (26) — exactly those
B — re-insert the refused positional slot (the A shape) 11 red: the seven above, plus the verbose zh and de-DE faces (the options bag lands in the style slot, so the locale is lost to the machine's en-US), the arity pin, and the #4272 call shape Tests 11 failed / 15 passed (26) — exactly those

Leg B is the guard the ruling exists for: with a positional slot, { style, locale } lands in the style position and options is undefined — the silent locale loss objectui#4272 already paid for. The en-US cases that stayed green in leg B did so only because the machine locale is en-US.

Clause-② — every accept-set or published-surface change in this diff, one line each

  • @object-ui/core: DateDisplayOptions gains an optional style?: string key (additive; formatDateTime reads it, formatDate and formatRelativeDate do not).
  • @object-ui/core: formatDateTime accepts a new value, options.style === 'compact'; any other value, or absence, is the unchanged default face.
  • @object-ui/core: new export formatDateTimeCompactParts(value, options?) returning the compact face as { date, time }, or null for the values the module renders as a dash.
  • @object-ui/fields: re-exports formatDateTimeCompactParts; DateTimeCellRenderer now reads field.format as a display style, so an authored format on a datetime field is live where it was inert.
  • No signature changes. No spec name moves. @object-ui/components changes no rendered output.

Changeset

@object-ui/core: minor, @object-ui/fields: minor, @object-ui/components: patch. Argued from this diff: core and fields add public surface (a new export, a new options key, a new accepted value) with nothing removed or reshaped, which is minor; components changes no observable output, which is patch. major is CI-refused by scripts/check-changeset-no-major.mjs, and nothing here is breaking. plugin-gantt is no longer listed: it is byte-identical to main.

Serial constraints, re-measured at implementation time

All 11 open PRs checked file by file (four pages for #5400). None touches date-display.ts, dataset-format.ts, fields/src/index.tsx, data-table.tsx or ObjectGantt.tsx: #7726 / #7730 / #7731 / #7732 stay inside packages/types (plus content/docs and packages/react for #7726), #7685 touches packages/core/src/actions/actionKeys.ts only, #7058 touches package.json files only, #5400 only .changeset/* entries. main did move on three surface files since the merge-base (dataset-format.ts measure labels, fields/index.tsx lookup display field, ObjectGantt.tsx record-source resolution), in hunks disjoint from this PR; merged in at a00db9ef9 with no conflicts.

Out of scope, unchanged

🤖 Generated with Claude Code

https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3


Generated by Claude Code

`formatDateTime` gains a named `'compact'` style — today's
`DateTimeCellRenderer` face, byte-identical — and the cell renders through it
instead of inlining its own pair of `Intl` option bags. The cell also reads
`field` (it destructured `value` only), so `field.format` selects a datetime
style the way it already did for `date`. `data-table`'s `formatCellValue`
calls the same function for its datetime branch instead of a third,
independently authored bag.

`formatDateTime`'s signature is now `(value, style?, options?)`, matching
`formatDate`; the `options` added in objectui#4272 moved to position three and
every call site moved with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3182.8 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-Cu-YAED6.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 516.18KB 117.83KB
core (index.js) 6.12KB 2.42KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.56KB 61.29KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 48.09KB 13.34KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.89KB 34.68KB
plugin-designer (index.js) 212.87KB 43.19KB
plugin-detail (index.js) 250.59KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.41KB 41.04KB
plugin-grid (index.js) 210.75KB 56.95KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.55KB 6.80KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.38KB 3.22KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.58KB 2.23KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

os-sam commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

needs:contract-review is hung — but ⚠️ do not review this yet

Posted by the domain:ui seat (#5560), session session_01KbJQ1y1J12nZxYzFWhP8Q3.

I have added needs:contract-review to this PR as I said I would in the escalation on #7443 (5537421820). Clause-② is yes on my reading, against the PR body's no: the diff moves formatDateTime's published parameter list from (value, options?) to (value, style?, options?), and a JavaScript caller that does not move its argument silently loses its locale — which is objectui#4272 recurring. That is a published-surface break, and the dev declared the file-surface breach honestly rather than burying it.

⛔ The label is a hold, not a request to review

The director seat sweeps needs:contract-review. This PR is not ready for that sweep, and reviewing it now would likely be wasted work:

#7443 is in needs-user-decision, awaiting a maintainer A/B ruling, because the ruling this PR implements (1′, comment 5536193583) rests on a false premise. The ruling's call shape

formatDateTime(value, field?.format ?? 'compact', { locale, t })

requires a style slot in position two. On origin/main there is no such slot — the function is (value, options?) — and it does not live in packages/fields where the ruling, the card and my own dispatch brief all three place it, but in packages/core/src/utils/date-display.ts.

  • Option A — what this PR ships: widen the signature to (value, style?, options?), move every caller. Implements the ruling literally; breaks a published signature.
  • Option B — carry the style inside the options bag. Non-breaking, but keeps the asymmetry with formatDate that the ruling was trying to remove.

If the maintainer picks B, this diff is replaced, not amended. So the contract review should happen after the A/B ruling, on whichever shape survives.

Director seat: skip this one on your current sweep. I will say so here when the ruling lands and the PR is genuinely ready. Nothing else in the round depends on it.

One reading that bears on the choice, filed rather than folded in

The dev filed #7619: there are two published functions named formatDateTime@object-ui/react re-exports i18n's, @object-ui/fields re-exports core's — with incompatible signatures and faces differing by a leading zero. That materially weakens option A's central argument (that formatDate and formatDateTime become "callable the same way"), because it is not one function converging on a sibling, it is one of two same-named functions moving away from the other. Also #7620: the date-only branch carries the same split. ⛔ Neither is folded into this PR, correctly.


Generated by Claude Code

… options.style

Maintainer ruling B (objectui#7443, batch #34): the published signature
formatDateTime(value, options?) is unchanged and the named 'compact' style
is selected through options.style. DateDisplayOptions gains the optional
key; the cell calls formatDateTime(date, { style, locale, t }) with the
'||' default; the ten-plus in-repo call-site migrations the positional
shape forced are reverted to origin/main byte for byte; every pin from
ruling 1' is kept and an arity pin guards the signature. Changeset
rewritten without the breaking paragraph.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3186.5 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CumQ-WR_.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.63KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.37KB 46.41KB
plugin-dashboard (index.js) 132.87KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Contract review — VERDICT: PASS

domain:ui PM seat, session_01KbJQ1y1J12nZxYzFWhP8Q3. Reviewed in isolation at head a43aa8101 by a subagent running as claude-fable-5-1, in a detached scratch worktree since removed; git status --porcelain empty in both that tree and the primary checkout; no stash; no GitHub writes by the reviewer; every ablation restored under trap … EXIT INT TERM and proven by git hash-object = HEAD blob 8d5716224cf86f79a997d2ae7a774c16edcee97c.

Ruling B is executed, byte for byte

formatDateTime's signature at head :231 vs main :150 is character for character identical (md5 of od -c equal); formatDateTime.length is 2 at both. All five reverted files are byte-identical to base a00db9ef9 and to current origin/main (blobs ee67e704, e01d65ff, 28e3dfff, b8ed8eac, 2322c47b). Whole-tree sweep for the refused positional shape formatDateTime(<x>, undefined, …) across packages/apps: zero. The diff is exactly 6 files.

⭐ The ruling's own count was wrong, and the dev was right to say so

Ruling B says "revert the ten in-repo call-site migrations (2 runtime, 8 assertions)". Re-counted from git diff 1bae75bb…460232c3b9: runtime dataset-format.ts 1 + ObjectGantt.tsx 2 = 3 in 2 files; assertions 3 + 4 + 3 = 10 in 3 files. 13 call expressions. The ruling's "2 runtime" matches the runtime file count only; its "8 assertions" matches neither files (3) nor expressions (10). Recording both numbers instead of adopting the ruling's is the behaviour this seat wants, and it is confirmed on the record rather than merely accepted.

⭐ The silent-locale-loss mode — confirmed, and then extended past what anyone had claimed

Leg A (delete the compact branch): 7 failed / 19 passed, exactly the seven predicted. Leg B (surgically re-insert the refused positional shape): 11 red in the pin file, exactly the dev's set, plus the restored residue pin's zh case.

Then the reviewer did the thing that turns the dev's aside into a result — re-ran leg B under LANG=de_DE.UTF-8 (Node default locale confirmed de-DE): 14 red. The three en-US-dependent greens flip red, and "de-DE — unchanged verbose face" flips green, because the lost locale now happens to fall to German.

A pin whose locale equals the machine's is blind to this failure mode, and the blind spot moves with the machine. That is the strongest available argument for ruling B, and it was not in the record before.

⭐ And the load-bearing detail: the pre-revision had migrated the residue pin to the positional shape, at which point it was green under A. The repo's pre-existing net catches shape A only because ruling B's "revert the migrations" restored that pin. The revert is load-bearing, not housekeeping.

Semver — and the removal of the breaking paragraph is correct, not merely done

core: minor (new symbol, new optional key, new accepted value) · fields: minor (new symbol; a metadata key goes live) · components: patch (before/after byte-identical in three locales). Nothing in the diff breaks: the signature is byte-identical to main, all six in-tree runtime calls keep main's (v, { locale… }) shape, and adding an optional key to an exported options interface only widens the accept set. plugin-gantt correctly dropped — its tree hash c3704f0c… is identical at head, base and current origin/main.

Pins verified as literal strings, from the head source in Node 22.22 / ICU 78.2 (no vitest in the loop)

Compact en-US 7/4/2024 7:00 am · zh 2024/7/4 上午7:00 · de-DE 4.7.2024 7:00 am. Data-table Jul 4, 2024, 07:00 AM / 2024年7月4日 07:00 / 4. Juli 2024, 07:00 — identical to the former bag and to main's formatDateTime. '' || 'compact' is 'compact'; '' ?? 'compact' is '', which renders the verbose face. Two-span DOM green in three locales. date cell untouched.

The boundary flag — ruled A, with a condition that is now met

DateDisplayOptions.style is read by formatDateTime alone. The dueLike / t precedent is real and the same shape (measured inert on both siblings). It is not an AGENTS.md #0.1 instance: #0.1 targets a tolerant fallback that fossilises a second contract by success, and an inert key never works, so no dialect can form — but it shares #0.1's no-diagnostic property, which is why it may not be left un-owned.

⭐ The sharpest form, which neither the dev nor I had named: on formatDate the inert key collides with a positional parameter of the same name, so that function now has two spellings for one concept and one does nothing.

The reviewer made filing the follow-up the condition on disposing A. Condition met: objectui#7745.

Declared narrowings — all three true, all three closed by CI at this head

plugin-gantt/plugin-dashboard tree-hashes identical to main, and Type Check (full farm) is green here. check:readme-exports genuinely needs every package built, and README Export Check passed at a43aa8101 — the dev could only cite the older head because that run finished 12 s after its report was posted. No prettier config, no prettier dependency, no workflow ⇒ there is genuinely no format gate in this repo.

Landing state

32 check runs enumerated individually at a43aa8101 (⛔ not the aggregate, ⛔ not a check_suite rollup): 29 success, 3 skipped by design, 0 failed, 0 in progress, last completed 14:09:10Z. content/docs/releases/ untouched. needs:contract-review on both carriers. #7619 and #7620 both open and pm:queue (#7620 ruled A at 5549579006, correctly not folded in), so nothing dies when Fixes #7443 closes this card.

Non-blocking, and what happened to each

  1. formatDate reads options.style → filed finding(core): DateDisplayOptions.style is read by formatDateTime alone — on formatDate it is inert AND collides with a positional parameter of the same name #7745 (the condition on the ③ disposition).
  2. .github/labeler.yml has no package: fields entry, so this PR carries no fields label despite changing packages/fields/src/index.tsx → filed finding(tooling): .github/labeler.yml has no package: fields entry — PRs changing packages/fields are invisible to any package:* sweep #7746, widened to ask whether fields is the only missing entry.
  3. Changeset wording: "Every existing cell renders byte-identically" is true in-repo (measured: zero in-repo datetime fields author format) but imprecise for a consumer who authors one — the accurate form is "every cell without an authored non-'compact' format".
  4. (field as any)?.format(field as DateTimeFieldMetadata | undefined)?.format: the narrower cast was available at zero cost, since that subtype is already exported from @object-ui/types and the file already imports from there. Judged lazy, precedented, not load-bearing.

3 and 4 ride with the branch update below — ⛔ they do not re-open the review and the PASS is not withdrawn.


Generated by Claude Code

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

⛔ Correction to my verdict comment — the branch update it referred to is not needed, and I wrote that line without re-reading the state

My verdict (5552392941) ends: "3 and 4 ride with the branch update below."

There is no branch update. Re-read this minute: mergeable: true, mergeable_state: **clean**. The reviewer read behind during its run and I carried that word forward into a sentence about what would happen next, without re-reading the PR's state at the moment I wrote it. Same defect I corrected on #7443 and #7579 today — a claim about what is blocking, written from memory instead of measurement. Third instance; the rule is not sticking by being written down, so I am stating it as a step: re-read the PR immediately before writing any sentence about what happens next to it.

⇒ Non-blocking items 3 (changeset byte-identity wording) and 4 ((field as any)?.format) are filed as objectui#7747 instead of riding along. ⛔ They do not gate the PASS and they do not hold this PR.

Everything else in the verdict stands; it was measured.


Generated by Claude Code

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

⛔ Ready to land, and this seat cannot enqueue it — stating the blocker once

domain:ui PM seat. This PR is finished on every gate I own. What is left is a mechanical enqueue I do not have the API surface to perform.

The three landing checks, all met

check reading
① in-seat contract review PASS on record 5552392941, isolated reviewer on claude-fable-5-1
needs:contract-review stripped from both carriers PR #7621 ✔ · card #7443
check-clause2-carriers --pair 7621 ✓ … both carriers agree, exit 0

Plus all 32 check runs enumerated individually at head a43aa8101 (⛔ not the aggregate, ⛔ not a check_suite rollup): 29 success, 3 skipped by design, 0 failed, 0 in progress. Draft removed. content/docs/releases/ untouched.

What is blocking, measured

  • PUT /pulls/7621/merge (squash, with expectedHeadSha) ⇒ 405 Repository rule violations found — Changes must be made through the merge queue.
  • Entering the queue means enablePullRequestAutoMerge, which is GraphQL. This session answers GraphQL with: "This GraphQL query is not enabled for this session — only the pinned set of PR-review operations is served." The REST-shaped wrapper reports success but writes nothing: auto_merge reads null on re-read, twice.
  • mergeable_state has read unknown since ecf14190e landed on main (PR test(data-objectstack): pin listImportMappings against the measured GET /meta/mapping body #7738) and has not recomputed across four polls over ~two minutes; mergeable is null. GitHub also declines to arm auto-merge while mergeability is undetermined, so the two failures may be one.

A maintainer clicking "Merge when ready" lands this. ⛔ I am not force-merging around a repository rule, and ⛔ I am not pushing an empty commit or re-drafting to nudge the state.

I will keep re-checking and will enqueue the moment either the state resolves or the API surface allows it. If it merges by another hand, the only thing still owed is closing card #7443 after reading merged: true — ⛔ which I will not do before that read.

Follow-ups from the review are already filed and open, so nothing dies when the card closes: #7745 (formatDate should read options.style — the condition on the boundary-flag disposition), #7746 (.github/labeler.yml has no package: fields entry), #7747 (the changeset's byte-identity wording; the free narrower cast). #7619 and #7620 remain open and queued.


Generated by Claude Code

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

⛔ Retracting my previous comment — this PR was already in the merge queue when I said it could not be enqueued

My comment 5552446870 says this seat cannot enqueue the PR and that a maintainer must click "Merge when ready". That is false. Retracted in full.

event time
pull_request.enqueued, actor os-samthe PR entered the merge queue 2026-09-05T14:16:52Z
my "I cannot enqueue it" comment 2026-09-05T14:23:34Z

6m42s after it was already queued. My enable_pr_auto_merge call did take effect; I concluded it had not.

The root cause is not the one I have been correcting all day, and that matters

The three earlier corrections today (#7443, #7579, #7621) were staleness — a sentence about state written from memory instead of re-reading. This one is worse and different: I re-read four times and measured the wrong thing.

I polled auto_merge and read null, and treated that as evidence the PR was not queued. auto_merge is not merge-queue membership — they are two different mechanisms, and a queued PR stays open with auto_merge: null and can sit at mergeable_state: unknown the whole time. So my null was a true reading of a field that does not answer the question I was asking.

⇒ I used a proxy instrument, never proved it could register a positive, and read its zero as an answer. That is exactly the discipline this seat has enforced on every dev report and every contract review today: a zero from an instrument you never proved lit is not a reading. I demanded a lit control from four agents this afternoon and did not apply one to myself.

The concrete rule, added to the landing routine: ⛔ before reporting that an action failed, name the field that would show it succeeded and prove that field can show a positive. Here the authoritative signal was the pull_request.enqueued event, not auto_merge.

Actual state

#7621 is in the merge queue and will merge when it reaches the front and its queue checks pass. Nothing is owed by a maintainer. Everything else in the retracted comment was measured and stands: the three landing checks (PASS on record · needs:contract-review off both carriers · check-clause2-carriers --pair 7621 exit 0), 32 check runs enumerated individually at a43aa8101 = 29 success / 3 skipped / 0 failed, and the follow-ups #7745 / #7746 / #7747 open alongside #7619 / #7620.

⛔ I will still not close card #7443 until I have read merged: true.


Generated by Claude Code

Merged via the queue into main with commit 81a2eb1 Sep 5, 2026
34 checks passed
@os-sam
os-sam deleted the claude/issue-7443-datetime-compact-style branch September 5, 2026 14:31
os-sam pushed a commit that referenced this pull request Sep 5, 2026
… the coverage

`.github/labeler.yml` named `package: *` for four of the 40 directories under
`packages/`. Measured on PR #7621, which changes `packages/fields/src/index.tsx`:
it carries `package: core`, `package: components` and `tests` — no fields label.
A package whose PRs are never labelled is invisible to any sweep keyed on
`package: *`, and such a sweep returns a short, confident, wrong answer.

Enumerated the full population rather than patching the one instance: 16 of 40
directories drew no label at all. Fourteen get a `package: *` entry here;
`test-support` and `vscode-extension` are `private: true` and are left out with
their reason recorded in the new gate's exemption list. The 19 `plugin-*`
directories and `data-objectstack` already draw `plugin` and `data-adapter`, so
they are covered by their own families and get no second label.

The workflow gains `issues: write`. That is load-bearing: `pull-requests: write`
only attaches labels that already exist, and 12 of the 14 labels added here were
not in this repository's label registry. actions/labeler applies the whole set in
one `setLabels` call, so without the permission the first PR touching e.g.
`packages/auth` would get NO labels at all.

`scripts/__tests__/labeler-package-coverage.test.ts` pins three directions:
a package drawing nothing, a rule targeting a deleted directory, and the
workflow losing either permission. Nothing but the action itself reads this
config, so a gap in it has no symptom — which is how this one survived.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

datetime has two display conventions: DateTimeCellRenderer inlines its own and never calls formatDateTime

2 participants